/*
Kakine
ActionScript Framework
Copyright © 2011, PUNY, LLC.

This file is part of KAKINE.

KAKINE is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.

KAKINE is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
GNU General Public License for more details.

You should have received a copy of the GNU General Public License
along with KAKINE.  If not, see <http://www.gnu.org/licenses/>.
*/ 
package com.puny.mvc.models
{
	import com.puny.mvc.ListenerManager;
	import com.puny.mvc.MvcEvent;
	
	import flash.events.Event;
	import flash.utils.Proxy;
	import flash.utils.flash_proxy;
		
	/**
	 * $Revision: 650 $ <br/>
	 * This defines model class of models for this MVC archtecture.
	 * An instance of this class can contain multiple variables like an Object class.
	 * When a value in this instance is updated, an event which type has the same as variable name will be dispatched.
	 * 
	 * @example
	 * <listing version="3.0">
	 * var model:Model = new Model();
	 * model.addEventListener("myValue", updatedHandler);
	 * 
	 * model["myValue"] = 100;
	 * 
	 * private function updatedHandler(event:Event):void {
	 * 	trace(event.currentTarget[event.type]); // 100
	 * }
	 * </listing>
	 * 
	 * A runtime error occurs when an undefined variable is accessed.
	 * 
	 * @example
	 * <listing version="3.0">
	 * var model:Model = new Model();
	 * model["name"] = "John";
	 * 
	 * trace(model["name"]);
	 * trace(model["age"]); // Runtime error because it's undefined.
	 * </listing>
	 */
	dynamic public class Model extends Proxy 
	{
		private const _model:DataStorage = new DataStorage();
		private const _updated:MvcEvent = new MvcEvent(MvcEvent.UPDATED);
		private const _events:Object = new Object();
		private var _beQuiet:Boolean = false;
		private var _listeners:ListenerManager;
		private var _dirty:Object = new Object();

		public static function commit(model:Model, index:int, arr:Array):void {
			model.commit();
		}
		
		/**
		 * Constructor <br/>
		 */
		public function Model()
		{
			_listeners = new ListenerManager();
		}
		
	    /**
	    * @private
	    */
	    override flash_proxy function callProperty(methodName:*, ... args):* {
	        var res:*;
			switch(methodName.toString()) {
				case 'addEventListener':
					_listeners.add(args[0], args[1]);
					res = _model.addEventListener(args[0],args[1],args[2],args[3],args[4]);
					_events[args[0]] = new Event(args[0]);
					break;
				case 'removeEventListener':
					if (_listeners != null) {
						_listeners.remove(args[0], args[1]);
					}
					res = _model.removeEventListener(args[0],args[1]);
//					delete _events[args[0]];
					break;
				default:
					res = _model[methodName].apply(_model, args);	        
				break;
			}	        
	        return res;
	    }
		
	    /**
	    * @private
	    */
	    override flash_proxy function getProperty(name:*):* {
	    	if (_model[name.localName]===undefined) {
	    		throw new Error("Undefined property:"+name.localName);
	    	}
	        return _model[name.localName];
	    }
	
	    /**
	    * @private
	    */
	    override flash_proxy function setProperty(name:*, value:*):void {
	        _dirty[name.localName] = (_model[name.localName] != value);
	        _model[name.localName] = value;
	        var event:Event = _events[name.localName];
	        if (event != null) {
		        dispatchUpdated(event);
		    }
	    }
		
		override flash_proxy function hasProperty(name:*):Boolean {
			return _model.hasOwnProperty(name.toString());
		}
		
		private function dispatchUpdated(event:Event):void {
			if (!_beQuiet&&_dirty[event.type]) {
				_model.dispatchEvent(event.clone());
				_dirty[event.type] = false;
			}
		}
	    
	    /**
	     * Stop dispatching an individual updated event
	     * and an <code>UPDATED</code> event will be fired after <code>commit</code> is called.
	     * This restrains multiple events are dispatched when multiple variables are updated at once.
	     * @example
	     * <listing versin="3.0">
	     * var model:Model = new Model();
	     * 
	     * model.addEventListener(MvcEvent.UPDATED, updatedHandler);
	     *
	     * model.beginUpdate();
	     * model["name"] = "Tom";
	     * model["age"] = 10;
	     * model.commit();
	     *
	     *private function updatedHandler(event:Event):void {
	     *		trace(event.currentTarget["name"], event.currentTarget["age"]); // Tom 10
	     *}
		 * </listing>
	     */
	    public function beginUpdate():void {
	    	_beQuiet = true;
	    }

		/**
		 * Dispatch all events that is related to updated values after
		 * <code>beginUpdate</code>.
		 * @see com.puny.mvc.models.Model#beginUpdate
		 */
		public function commit():void {
			if (_beQuiet) {
				_beQuiet = false;
				for (var type:String in _dirty) {
					if (_dirty[type]) {
						if (_events[type] != null) {
							dispatchUpdated(_events[type]);
						}
						_dirty[type] = false;
					}
				}
			}
		}
		
		/**
		 * Release all variables and event listeners. 
		 * 
		 */
		public function cleanUp():void {
			for(var label:* in _model) {
				this[label] = undefined;
			}
			_listeners.cleanUpListeners(_model);
		}
		
		/**
		 * Return a string oof set of all variable names and values.
		 * @return set of all variable names and values.
		 */
		public function toString():String {
			var string:String = "";
			for(var label:* in _model) {
				if (this[label] is Array) {
					string +=  " "+label+" = "+this[label]+"\n";
				} else {
					string +=  " "+label+" = \""+this[label]+"\"\n";
				}
			}
			return string;
		}
		
		/**
		 * 
		 * @param attributes　xml attributes
		 * @example
		 * <listing version="3.0">
		 * var xml:XML = new XML("&lt;myElement name='Bob' age='12'/&gt;");
		 * var model:Model = new Model();
		 * model.parseAttributes(xml.attributes());
		 * 
		 * trace(model["name"]); // Bob
		 * trace(model["age"]);  // 12
		 * </listing>
		 */
		public function parseAttributes(attributes:XMLList):void {
			for each(var attr:XML in attributes) {
				this[attr.name()] = attr;
			}
		}			

		/**
		 * 
		 * @return internal data
		 * @private
		 */
		public function get data():* {
			return _model;
		}			
	}
	
}